<template>
  <form @submit.prevent="submitForm">
    <div v-for="field in fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <input
        :type="field.type"
        :name="field.name"
        :value="formData[field.name]"
        @input="updateFormData($event, field.name)"
      />
      <span v-if="errors[field.name]" class="error">{{ errors[field.name] }}</span>
    </div>
    <button type="submit">Submit</button>
  </form>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";

export default defineComponent({
  name: "MyForm",
  props: {
    fields: {
      type: Array,
      required: true,
    },
    customValidators: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props, { emit }) {
    const state = reactive({
      formData: {} as Record<string, string>,
      errors: {} as Record<string, string>,
    });

    const updateFormData = (event: Event, name: string) => {
      const target = event.target as HTMLInputElement;
      state.formData[name] = target.value;
    };

    const validateField = (name: string, value: string) => {
      const validators = props.customValidators[name];
      if (validators) {
        const error = validators.find(
          (validator: (arg0: string) => any) => !validator(value)
        );
        if (error) {
          state.errors[name] = error;
        } else {
          delete state.errors[name];
        }
      }
    };

    const submitForm = () => {
      Object.keys(state.formData).forEach((name) => {
        validateField(name, state.formData[name]);
      });
      if (Object.keys(state.errors).length === 0) {
        emit("submit", state.formData);
      }
    };

    return { ...toRefs(state), updateFormData, submitForm };
  },
});
</script>

<style scoped>
.error {
  color: red;
}
</style>
